package modlgen

import (
	"bufio"
	l4g "code.google.com/p/log4go"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

type fileReader struct {
	filename                string
	file                    *os.File
	reader                  *bufio.Reader
	lineIdx                 int
	currentContentLine      string
	currentContentLineParts []string
}

func Generate(baseDir, srcPkgDir string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			l4g.Critical("Error: %s", r)
			err = errors.New("")
		}
	}()
	types := newMdoTypeDefSet()
	for _, filename := range genFileList(baseDir, srcPkgDir) {
		parseFile(filename, types)
	}
	types.resolveHierarchy()
	types.generate(baseDir, srcPkgDir)
	return nil
}

func genFileList(baseDir, srcPkgDir string) []string {
	pattern := filepath.Join(srcPkgDir, "*.mdo")
	l4g.Trace("Executing match using pattern %s", pattern)
	fileList, err := filepath.Glob(pattern)
	if err != nil {
		panic(fmt.Sprintf("%s", err))
	}
	if fileList == nil {
		l4g.Info("No .mdo files found in %s", srcPkgDir)
		return []string{}
	}
	return fileList
}

func parseFile(filename string, types *mdoTypeDefSet) {
	defer func() {
		if r := recover(); r != nil {
			if r == io.EOF {
				return
			} else {
				panic(r)
			}
		}
	}()
	l4g.Trace("Parsing file %s", filename)
	currentFileReader := initFileReader(filename)
	defer currentFileReader.close()
	for {
		switch currentFileReader.currentContentLineParts[0] {
		case "type":
			types.add(newMdoTypeDef(currentFileReader))
		default:
			panic(fmt.Sprintf("Unrecognized keyword \"%s\" on reader.currentContentLine %d of file %s", currentFileReader.currentContentLineParts[0], currentFileReader.lineIdx, currentFileReader.filename))
		}
		currentFileReader.getNextContentLine()
	}

}

func initFileReader(filename string) *fileReader {
	reader := new(fileReader)
	reader.filename = filename
	var err error
	if reader.file, err = os.Open(filename); err != nil {
		panic(fmt.Sprintf("Couldn't open file \"%s\", error: %s", filename, err))
	}
	reader.reader = bufio.NewReader(reader.file)
	reader.getNextContentLine()
	return reader
}

func (reader *fileReader) getNextContentLine() {
	inBlockComment := false
	var err error
	for {
		reader.lineIdx++
		reader.currentContentLine, err = reader.reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				if len(reader.currentContentLine) == 0 {
					panic(err)
				}
			} else {
				panic(fmt.Sprintf("Caught error while reading reader.currentContentLine %d of file %s. Error: %s", reader.lineIdx, reader.filename, err))
			}
		}
		reader.currentContentLine = strings.TrimSpace(reader.currentContentLine)
		if reader.currentContentLine == "" {
			continue
		}
		if strings.HasPrefix(reader.currentContentLine, "//") {
			continue
		}
		if strings.HasPrefix(reader.currentContentLine, "/*") {
			inBlockComment = true
			continue
		}
		if inBlockComment {
			if strings.HasSuffix(reader.currentContentLine, "*/") {
				inBlockComment = false
			}
			continue
		}
		reader.currentContentLineParts = strings.Fields(reader.currentContentLine)
		return
	}
	return
}

func (reader *fileReader) getNextLine() {
	var err error
	reader.lineIdx++
	reader.currentContentLine, err = reader.reader.ReadString('\n')
	if err != nil {
		if err == io.EOF {
			panic(err)
		}
		panic(fmt.Sprintf("Caught error while reading reader.currentContentLine %d of file %s. Error: %s", reader.lineIdx, reader.filename, err))
	}
	reader.currentContentLine = strings.TrimSpace(reader.currentContentLine)
	reader.currentContentLineParts = strings.Fields(reader.currentContentLine)
}

func (reader *fileReader) close() {
	reader.file.Close()
}
